#pragma once

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<functional>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<signal.h>
#include<pthread.h>
#include"err.hpp"
#include"Task.hpp"
#include"ThreadPool_V3.hpp"
#include"log.hpp"

namespace ns_server//命名空间
{
    static const uint16_t defaultport = 8081;
    static const int backlog = 32;
    //函数类型
    //返回值是string,函数参数是string&
    using func_t = std::function<std::string(const std::string&)>;
    class TcpServer;
    class ThreadData
    {
    public:
        ThreadData(int sock,const std::string& ip,const uint16_t& port,TcpServer* ts)
                :sock(sock),
                clientip(ip),
                clientport(port),
                current(ts)
                {}
    public:
        int sock;
        std::string clientip;
        uint16_t clientport;
        TcpServer* current;
    };
    class TcpServer
    {
    public:
        TcpServer(func_t func,uint16_t port = defaultport)
                    :func_(func),
                    port_(port),
                    quit_(true)
        {}
        //在初始化tcp服务器的时候，只有创建套接字成功，绑定成功，监听成功，此时tcp服务器的初始化
        //才算完成
        void initServer()
        {
            //1.创建套接字:网络文件,返回文件描述符
            listensock_ = socket(AF_INET,SOCK_STREAM,0);
            if(listensock_<0)
            {
                //std::cerr<<"create socket error"<<std::endl;
                //致命错误
                logMessage(Fatal,"Create socket error,code: %d, error string:%s",errno,strerror(errno));
                exit(SOCKET_ERR);
            }
            logMessage(Info,"Create socket success, code: %d, error string:%s",errno,strerror(errno));
            //2.bind绑定,先填充结构体属性
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(port_);//主机字节序转网络字节序
            local.sin_addr.s_addr = htonl(INADDR_ANY);

            if(bind(listensock_,(struct sockaddr*)&local,sizeof(local))<0)
            {
                //std::cerr << "bind socket error" << std::endl;
                logMessage(Fatal,"bind socket error,code: %d, error string:%s",errno,strerror(errno));
                exit(SOCKET_ERR);   
            }
            logMessage(Info,"bind socket success,code: %d, error string:%s",errno,strerror(errno));
            
            //3.监听:【监听是否有新的的客户端连接到来】，如果监听失败就没有必要进行后序操作
            //因为监听失败意味着tcp服务器无法接收客户端发来的连接请求,因此监听失败就直接终止程序。
            //int listen(int sockfd,int backlog)
            if(listen(listensock_,backlog)<0)
            {
                //std::cerr << "listen socket error" << std::endl;
                logMessage(Fatal,"Listen socket error,code: %d, error string:%s",errno,strerror(errno));
                exit(LISTEN_ERR); 
            }
            logMessage(Info,"Listen socket success,code: %d, error string:%s",errno,strerror(errno));
        }
        void start()
        {
            //实际当子进程退出时会给父进程发送SIGCHILD信号，如果父进程将SIGCHILD
            //进行捕捉，并将该信号的处理动作设置为忽略，此时父进程就只需转心处理自己的工作
            //不必关心子进程了
            signal(SIGCHLD,SIG_IGN);

            quit_ = false;
            while(!quit_)
            {
                //4.获取连接，accept,填充客户端属性ip和端口号
                //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
                //accept的返回值也会是一个文件描述符(这是好再来餐馆里面真正提供服务的服务员)
                //是真的套接字
                struct sockaddr_in client;
                socklen_t len = sizeof(client);
                //由于该主进程是会不断的去获取客户端连接的，所以sock文件描述符会被重复申请
                //所以一定要注意关闭sock
                int sock = accept(listensock_,(struct sockaddr*)&client,&len);
                if(sock<0)
                {
                    //std::cerr <<"accept error"<<std::endl;
                    logMessage(Warning,"accept error,code: %d, error string:%s",errno,strerror(errno));
                    continue;//继续获取连接
                }
                //提取客户端client信息--debug
                std::string clientip = inet_ntoa(client.sin_addr);
                uint16_t clientport = ntohs(client.sin_port);
                //5.获取新连接成功,开始进行业务处理
                //std::cout<<"获取新连接成功: "<<sock<<"from"<<listensock_<<","
                //    <<clientip<<"-"<<clientport<<std::endl;//4 from 3
                logMessage(Info, "获取新连接成功: %d from %d, who: %s - %d",
                           sock, listensock_, clientip.c_str(), clientport);

                //V1版本：单执行流服务器
                //service(sock,clientip,clientport);//业务

                //V2版本:多进程版本
                //对于套接字文件也是一样，父进程创建的子进程也会继承父进程的套接字文件
                //以及文件描述符表
                // pid_t id = fork();
                // if(id<0)//创建子进程失败
                // {
                //     close(sock);//记得关闭套接字
                //     continue;
                // }
                // else if(id==0)
                // {
                //     //子进程能够对特定的套接字文件进行读写操作(类匿名管道),所以子进程要先关闭它不需要的fd
                //     //以免对监听套接字误操作
                //     close(listensock_);
                //     //在父进程中，fork成功返回子进程pid
                //     //即我们也可以让服务端创建出来的子进程再次fork，让孙子进程为客户端提供服务
                //     //此时我们就不需要等待孙子进程退出了
                //     if(fork()>0) exit(0);
                //     //child已经退了，孙子进程在运行,变成了孤儿进程，该进程会被系统领养
                //     //当孙子进程为客户端提供完服务退出后，系统会回收孙子进程的。
                //     //所以爷爷进程不需要等待孙子进程退出，只需要等待他的子进程退出。
                //     service(sock,clientip,clientport);
                //     exit(0);//子进程提供完服务退出
                // }
                // //对于主进程，当他调用fork函数后就必须将从accept函数获取的sock文件描述符
                // //关闭！！！因为该进程会不断调用accept函数获取客户端连接，即会不断获取
                // //新的服务套接字(文件描述符),最终会导致该进程可用的文件描述符越来越少。
                // close(sock);
                // pid_t ret = waitpid(id, nullptr, 0); //阻塞的! waitpid(id, nullptr, WNOHANG);//不推荐
                // if(ret==id) std::cout<<"wait child"<<id<<"success"<<std::endl;

                //V3版本：原生多线程,创建多进程的成本很高。
                //而线程的本质是在进程地址空间内运行，创建出来的线程会共享该进程的大部分资源
                //因此在实现多执行流的服务器时最好采用多线程实现。
                // pthread_t tid;
                // ThreadData* td = new ThreadData(sock,clientip,clientport,this);
                // pthread_create(&tid,nullptr,threadRoutine,td);

                //V3缺陷：每当有新连接到来时，服务端的主线程都会重新为该客户端创建为其提供服务的新线程，而当服务结束后又会将该新线程销毁。
                //这样做不仅麻烦，而且效率低下，每当连接到来的时候服务端才创建对应提供服务的线程。
                //V4:线程池

                
                Task t(sock, clientip, clientport, std::bind(&TcpServer::service, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
                ThreadPool<Task>::getinstance()->pushTask(t);
            }
        }
        static void* threadRoutine(void* args)
        {
            pthread_detach(pthread_self());//主线程不需要join,该线程自己分离
            ThreadData* td = static_cast<ThreadData*>(args);
            td->current->service(td->sock,td->clientip,td->clientport);
            delete td;
            return nullptr;
        }
        //a.先实现一个基本的读写服务
        //udp:面向数据报
        //tcp:面向字节流
        //需要注意的是，服务端读取数据是服务套接字中读取的，而写入数据也是写入服务套接字的
        //也就是这里为客户端提供的套接字，既可以读取也可以写入，这就是TCP全双工通信的体现。
        void service(int sock,const std::string& clientip,const uint16_t& clientport)
        {
            std::string who = clientip + "-" + std::to_string(clientport);
            char buffer[1024];
            while(true)
            {
                //读取客户端消息
                ssize_t s = read(sock,buffer,sizeof(buffer)-1);
                if(s>0)
                {
                    buffer[s]=0;
                    //回调函数,执行完函数完毕后，将结果返回,并打印
                    std::string res = func_(buffer);
                    //std::cout<<who<<">>>"<<res<<std::endl;//这是回显给服务器的
                    //打印日志信息：DEBUG
                    //客户信息+服务器返回给用户的业务处理的结果
                    logMessage(Debug,"%s# %s",who.c_str(),res.c_str());
                    //将业务处理的结果写回
                    write(sock,res.c_str(),res.size());
                }
                else if(s==0)
                {
                    //read读到0，表示读到了文件结尾，在网络中表明客户端把连接关闭了
                    //当前线程服务对于客户端完毕，记得关闭sock网络套接字(文件描述符)
                    //否则会导致文件描述符泄漏
                    close(sock);
                    //std::cout << who << " quit, me too" << std::endl;
                    //打印日志
                    logMessage(Info,"%s,quit,me too",who.c_str());
                    break;
                }
                else
                {
                    close(sock);
                    //std::cerr<<"read error: "<<strerror(errno)<<std::endl;
                    logMessage(Error,"read error,%d:%s",errno,strerror(errno));
                    break;
                }
            }
        }
        ~TcpServer()
        {}
    private:
        //只要是服务器，不论是udp还是tcp，都有【端口号】
        uint16_t port_;
        int listensock_;//监听套接字(相当于张三，揽客但不提供服务，只是监听是否有客户到来)
        bool quit_;//表示服务器是否退出，true则表示服务器没有运行
        func_t func_;
    };
}